//
//  HHQueuePool.swift
//  QueueToolBox
//
//  Created by kingdee on 2021/11/25.
//

import Foundation

fileprivate let backgroundStr = "background"
fileprivate let utilityStr = "utility"
fileprivate let defaultStr = "default"
fileprivate let userInitiatedStr = "userInitiated"
fileprivate let userInteractiveStr = "userInteractive"

fileprivate let qoSStringMap: [String: DispatchQoS] = [backgroundStr: .background,
                                                          utilityStr: .utility,
                                                          defaultStr: .default,
                                                    userInitiatedStr: .userInitiated,
                                                  userInteractiveStr: .userInteractive]

fileprivate func qosToString(qos: DispatchQoS) -> String {
    switch qos {
    case .background:
        return backgroundStr
    case .utility:
        return utilityStr
    case .default:
        return defaultStr
    case .userInitiated:
        return userInitiatedStr
    case .userInteractive:
        return userInteractiveStr
    default:
        return defaultStr
    }
}

fileprivate func _synchronized<T>(_ lock: AnyObject, _ closure: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try closure()
}


@objcMembers fileprivate class HHQueuesInfo: NSObject {
    
    let qos: DispatchQoS
    let attr: DispatchQueue.Attributes
    var name: String = ""
    fileprivate var queues: [DispatchQueue] = []
    fileprivate var index: Int = 0
    fileprivate var maxCount: Int = 1
    
    private init(dispatchQos: DispatchQoS, attribute: DispatchQueue.Attributes) {
        qos = dispatchQos
        attr = attribute
        super.init()
    }
    
    convenience init(dispatchQos: DispatchQoS, attribute: DispatchQueue.Attributes, maxQueueCount: Int = 1) {
        self.init(dispatchQos: dispatchQos, attribute: attribute)
        maxCount = maxQueueCount
    }
    
    func getQueue() -> DispatchQueue {
        _synchronized(self) {
            var result: DispatchQueue!
            let queuesCount = self.queues.count
            if queuesCount >= maxCount {
                if index > queuesCount - 1 {
                    index = 0
                }
                result = queues[index]
                index += 1
            } else {
                let queue = creatQueue(index)
                queues.append(queue)
                index = queues.count
                result = queue
            }
            return result
        }
    }
    
    fileprivate func creatQueue(_ index: Int) -> DispatchQueue {
        debugPrint("createQueue: \(index), \(qos), \(attr), \(name)")
        var label = name
        if name.utf16.count > 0 {
            label = name + "\(index)"
        }
        let result: DispatchQueue = DispatchQueue(label: label, qos: self.qos, attributes: self.attr, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil)
        return result
    }
}


@objcMembers class HHQueuePool: NSObject {
    
    static let pool = HHQueuePool()
    private override init() {
        perMaxCount = ProcessInfo.processInfo.activeProcessorCount
    }
    
    fileprivate var perMaxCount = 1
    fileprivate var serialQueueMap: [String: HHQueuesInfo] = [:]
    fileprivate var concurrentQueueMap: [String: HHQueuesInfo] = [:]
    
    func serialQueue(name: String, qos: DispatchQoS) -> DispatchQueue {
        
        let targetQueue = getSerialTargetQueue(qos: qos)
        let queue = DispatchQueue(label: name, qos: qos, attributes: DispatchQueue.Attributes.init(), autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: targetQueue)
        return queue
    }
    
    func concurrentQueue(name: String, qos: DispatchQoS) -> DispatchQueue {
        
        let targetQueue = getConcurrentTargetQueue(qos: qos)
        let queue = DispatchQueue(label: name, qos: qos, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: targetQueue)
        return queue
    }
    
    fileprivate func getSerialTargetQueue(qos: DispatchQoS) -> DispatchQueue {
        
        var result: DispatchQueue!
        let flagStr = qosToString(qos: qos)
        if let queuesInfo = self.serialQueueMap[flagStr] {
            result = queuesInfo.getQueue()
        } else {
            _synchronized(self) {
                if self.serialQueueMap[flagStr] == nil {
                    let queueInfo = HHQueuesInfo(dispatchQos: qos, attribute: DispatchQueue.Attributes.init(), maxQueueCount: self.perMaxCount)
                    queueInfo.name = "com.queuePool.serial."
                    self.serialQueueMap[flagStr] = queueInfo
                    result = queueInfo.getQueue()
                }
            }
        }
        return result
    }
    
    fileprivate func getConcurrentTargetQueue(qos: DispatchQoS) -> DispatchQueue  {
        var result: DispatchQueue!
        let flagStr = qosToString(qos: qos)
        if let queuesInfo = self.concurrentQueueMap[flagStr] {
            result = queuesInfo.getQueue()
        } else {
            _synchronized(self) {
                if self.concurrentQueueMap[flagStr] == nil {
                    let queueInfo = HHQueuesInfo(dispatchQos: qos, attribute: DispatchQueue.Attributes.concurrent, maxQueueCount: 1)
                    queueInfo.name = "com.queuePool.concurrent."
                    self.concurrentQueueMap[flagStr] = queueInfo
                    result = queueInfo.getQueue()
                }
            }
        }
        return result
    }
}
